/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 battlefield_terrain_map.cpp

	$Header: /resource_editor/battlefield_terrain_map.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "battlefield_terrain_map.h"

#include <cassert>
#include <cstdlib>

#include "adventure_tile_size.h"
#include "battlefield_metrics.h"
#include "direction.h"
#include "enum_operations.h"
#include "isometric_map.h"
#include "map_point.h"
#include "map_point_list.h"
#include "metatile_map.h"
#include "quad.h"
#include "screen_point.h"
#include "shared_ptr.h"
#include "transition_calculator.h"

// -----------------------------------------------------------------------
// Unnamed namespace
// -----------------------------------------------------------------------

namespace
{

	// -----------------------------------------------------------------------
	// -----------------------------------------------------------------------
	inline t_screen_point internal_get_view_size()
	{
		return	t_screen_point(
					k_battlefield_logical_view_width,
					k_battlefield_logical_view_height );
	}

}

// -----------------------------------------------------------------------
// t_battlefield_terrain_map::t_impl class
// -----------------------------------------------------------------------

class t_battlefield_terrain_map::t_impl
	:	public t_metatile_map::t_abstract_tile_map
{
public:
	// Data members
	t_isometric_map< t_tile >				m_tile_map;
	t_isometric_vertex_map< t_tile_vertex >	m_vertex_map;
	t_shared_ptr< t_metatile_map >			m_metatile_map_ptr;

	// Constructor
	t_impl();

	// Member functions
	int				get_size() const;
	virtual void	get_size( int & size, int & num_levels ) const;
	t_tile &		get_tile( t_map_point_2d const & point );
	t_tile const &	get_tile( t_map_point_2d const & point ) const;
	virtual void	get_row_bounds( int row, int & start, int & end ) const;
	int				get_row_end( int row ) const;
	int				get_row_start( int row ) const;
	t_screen_point	get_view_size() const;
	bool			is_valid( t_map_point_2d const & point ) const;
};

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_impl::t_impl()
	:	m_tile_map( internal_get_view_size(), k_adventure_tile_height ),
		m_vertex_map( internal_get_view_size(), k_adventure_tile_height )
{
	m_metatile_map_ptr = new t_metatile_map( *this, rand() );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline int t_battlefield_terrain_map::t_impl::get_size() const
{
	return m_tile_map.get_size();
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::t_impl::get_size( int & size, int & num_levels ) const
{
	size = m_tile_map.get_size();
	num_levels = 1;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline t_battlefield_terrain_map::t_tile & t_battlefield_terrain_map::t_impl::get_tile(
	t_map_point_2d const & point )
{
	return m_tile_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline t_battlefield_terrain_map::t_tile const & t_battlefield_terrain_map::t_impl::get_tile(
	t_map_point_2d const & point ) const
{
	return m_tile_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline int t_battlefield_terrain_map::t_impl::get_row_end( int row ) const
{
	return m_tile_map.get_row_end( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline int t_battlefield_terrain_map::t_impl::get_row_start( int row ) const
{
	return m_tile_map.get_row_start( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::t_impl::get_row_bounds( int row, int & start, int & end ) const
{
	assert( row >= 0 && row < m_tile_map.get_size() );
	start = m_tile_map.get_row_start( row );
	end = m_tile_map.get_row_end( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline t_screen_point t_battlefield_terrain_map::t_impl::get_view_size() const
{
	return m_tile_map.get_view_size();
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
inline bool t_battlefield_terrain_map::t_impl::is_valid( t_map_point_2d const & point ) const
{
	return m_tile_map.is_valid( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// t_battlefield_terrain_map members
// -----------------------------------------------------------------------

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_screen_point t_battlefield_terrain_map::get_view_size()
{
	return internal_get_view_size();
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_battlefield_terrain_map()
	:	m_impl_ptr( new t_impl )
{
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_battlefield_terrain_map( t_battlefield_terrain_map const & other )
	:	m_impl_ptr( new t_impl( *other.m_impl_ptr ) )
{
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::~t_battlefield_terrain_map()
{
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::establish_transitions()
{
	t_impl & impl = *m_impl_ptr;

	t_transition_calculator transition_calculator;

	int map_size = impl.m_tile_map.get_size();
	t_level_map_point_2d point;
	for ( point.row = 0; point.row < map_size; ++point.row )
	{
		int row_start = impl.m_tile_map.get_row_start( point.row );
		int row_end = impl.m_tile_map.get_row_end( point.row );

		for ( point.column = row_start; point.column < row_end; ++point.column )
			transition_calculator.set_transitions( impl, point );
	}
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_metatile_map const & t_battlefield_terrain_map::get_metatile_map() const
{
	return *m_impl_ptr->m_metatile_map_ptr;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_screen_point t_battlefield_terrain_map::get_screen_point( t_map_point_2d const & point ) const
{
	return m_impl_ptr->m_tile_map.get_screen_point( point );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
int t_battlefield_terrain_map::get_size() const
{
	return m_impl_ptr->m_tile_map.get_size();
}

int t_battlefield_terrain_map::get_vertex_size() const
{
	return m_impl_ptr->m_vertex_map.get_size();
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_tile &
t_battlefield_terrain_map::get_tile( t_map_point_2d const & point )
{
	return m_impl_ptr->m_tile_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_tile const &
t_battlefield_terrain_map::get_tile( t_map_point_2d const & point ) const
{
	return m_impl_ptr->m_tile_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
int t_battlefield_terrain_map::get_tile_row_end( int row ) const
{
	return m_impl_ptr->m_tile_map.get_row_end( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
int t_battlefield_terrain_map::get_tile_row_start( int row ) const
{
	return m_impl_ptr->m_tile_map.get_row_start( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_tile_vertex &
t_battlefield_terrain_map::get_tile_vertex( t_map_point_2d const & point )
{
	return m_impl_ptr->m_vertex_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map::t_tile_vertex const &
t_battlefield_terrain_map::get_tile_vertex( t_map_point_2d const & point ) const
{
	return m_impl_ptr->m_vertex_map.get( t_level_map_point_2d( point, 0 ) );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_quad< t_battlefield_terrain_map::t_tile_vertex & >
t_battlefield_terrain_map::get_tile_vertex_quad( t_map_point_2d const & point )
{
	t_impl & impl = *m_impl_ptr;
	t_level_map_point_2d work_point( point, 0 );
	t_tile_vertex & top = impl.m_vertex_map.get( work_point );
	++work_point.row;
	t_tile_vertex & left = impl.m_vertex_map.get( work_point );
	++work_point.column;
	t_tile_vertex & bottom = impl.m_vertex_map.get( work_point );
	--work_point.row;
	t_tile_vertex & right = impl.m_vertex_map.get( work_point );

	return t_quad< t_tile_vertex & >( left, top, right, bottom );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_quad< t_battlefield_terrain_map::t_tile_vertex const & >
t_battlefield_terrain_map::get_tile_vertex_quad( t_map_point_2d const & point ) const
{
	t_impl const & impl = *m_impl_ptr;
	t_level_map_point_2d work_point( point, 0 );
	t_tile_vertex const & top = impl.m_vertex_map.get( work_point );
	++work_point.row;
	t_tile_vertex const & left = impl.m_vertex_map.get( work_point );
	++work_point.column;
	t_tile_vertex const & bottom = impl.m_vertex_map.get( work_point );
	--work_point.row;
	t_tile_vertex const & right = impl.m_vertex_map.get( work_point );

	return t_quad< t_tile_vertex const & >( left, top, right, bottom );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
int t_battlefield_terrain_map::get_tile_vertex_row_end( int row ) const
{
	return m_impl_ptr->m_vertex_map.get_row_end( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
int t_battlefield_terrain_map::get_tile_vertex_row_start( int row ) const
{
	return m_impl_ptr->m_vertex_map.get_row_start( row );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
bool t_battlefield_terrain_map::is_valid( t_map_point_2d const & point ) const
{
	return m_impl_ptr->m_tile_map.is_valid( point );
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::recalculate_vertex_lighting()
{
	t_impl & impl = *m_impl_ptr;

	t_map_point_2d vertex_point;
	for (	vertex_point.row = 0;
			vertex_point.row < impl.m_vertex_map.get_size();
			++vertex_point.row )
	{
		int row_start = impl.m_vertex_map.get_row_start( vertex_point.row );
		int row_end = impl.m_vertex_map.get_row_end( vertex_point.row );
		for (	vertex_point.column = row_start;
				vertex_point.column < row_end;
				++vertex_point.column )
		{
			t_tile_vertex & tile_vertex = impl.m_vertex_map.get( t_level_map_point_2d( vertex_point, 0 ) );
			int new_light;

			if ( vertex_point.column > row_start )
			{
				t_level_map_point_2d left_vertex_point( vertex_point.row, vertex_point.column - 1, 0 );
				int left_height = impl.m_vertex_map.get( left_vertex_point ).get_height();

				if ( vertex_point.column < row_end - 1 )
				{
					int left_light = compute_edge_light( left_height, tile_vertex.get_height() );

					t_level_map_point_2d right_vertex_point( vertex_point.row, vertex_point.column + 1, 0 );
					int right_height = impl.m_vertex_map.get( right_vertex_point ).get_height();
					int right_light = compute_edge_light( tile_vertex.get_height(), right_height );

					new_light = ( left_light + right_light ) / 2;
				}
				else
					new_light = compute_edge_light( left_height, tile_vertex.get_height() );
			}
			else
			{
				assert( vertex_point.column < row_end - 1 );
				t_level_map_point_2d right_vertex_point( vertex_point.row, vertex_point.column + 1, 0 );
				int right_height = impl.m_vertex_map.get( right_vertex_point ).get_height();
				new_light = compute_edge_light( tile_vertex.get_height(), right_height );
			}

			tile_vertex.set_light( new_light );
		}
	}
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_battlefield_terrain_map & t_battlefield_terrain_map::operator=( t_battlefield_terrain_map const & other )
{
	m_impl_ptr.reset( new t_impl( *other.m_impl_ptr ) );
	return *this;
}

// -----------------------------------------------------------------------
// reset height of a vertex
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::reset_height( t_map_point_2d const& point,
						                      int height )
{
	t_map_point_2d_list open_points;
	t_direction			direction;
	t_map_point_2d      map_point;
	t_map_point_2d      adjacent_point;
	int				    adjacent_height;
	int					delta;
	int					limit;
	t_battlefield_terrain_tile_vertex* vertex;

	vertex = &get_tile_vertex( point );
	if (vertex->get_height() == height)
		return;
	vertex->set_height( height );
	open_points.push_back( point );
	while (!open_points.empty())
	{
		// check adjacent points to see if they violate the slope restrictions
		map_point = open_points.front();
		height = get_tile_vertex( map_point ).get_height();
		open_points.pop_front();
		for (direction = t_direction(0); direction < k_direction_count; enum_incr(direction))
		{
			adjacent_point = map_point + get_direction_offset( direction );
			if (!is_valid( adjacent_point ))
				continue;
			vertex = &get_tile_vertex( adjacent_point );
			adjacent_height = vertex->get_height();
			delta = adjacent_height - height;
			if (direction & 1)
				limit = 24;
			else
				limit = 16;
			if (abs( delta ) <= limit)
				continue;
			if (delta < 0)
				vertex->set_height( height - limit );
			else
				vertex->set_height( height + limit );
			open_points.push_back( adjacent_point );
		}
	}
}

// -----------------------------------------------------------------------
// reset river tiles
// -----------------------------------------------------------------------
void t_battlefield_terrain_map::set_river_height()
{
	t_map_point_2d point;
	t_map_point_2d offset;
	t_map_point_2d new_point;
	int			   size = get_size();
	int			   row_end;
	t_terrain_type terrain;

	for (point.row = 0; point.row < size; point.row++)
	{
		point.column = get_tile_row_start( point.row );
		row_end = get_tile_row_end( point.row );
		for (; point.column < row_end; point.column++)
		{
			terrain = get_tile( point ).get_terrain();
			if (terrain != k_terrain_water_river && terrain != k_terrain_ice_river)
				continue;
			for (offset.row = 0; offset.row <= 1; offset.row++)
			{
				for (offset.column = 0; offset.column <= 1; offset.column++)
				{
					new_point = point + offset;
					if (is_valid( new_point ))
						reset_height( new_point, k_battlefield_base_height );
				}
			}
		}	
	}
}

// -----------------------------------------------------------------------
// read / write from saved game
// -----------------------------------------------------------------------
static int const k_current_version = 0;

bool t_battlefield_terrain_map::read( std::streambuf& stream )
{
	t_map_point_2d	point;
	t_map_point_2d	offset;
	t_map_point_2d	new_point;
	int				size = get_size();
	int				row_size;
	t_tile*			tile;
	int				version = get<t_int16>( stream );
	int				tile_version = get<t_int16>( stream );

	if (version < 0 || version > k_current_version)
		return false;
	for (point.row = 0; point.row < size; point.row++)
	{
		point.column = get_tile_row_start( point.row );
		row_size = get_tile_row_end( point.row ) - point.column;
		tile = &get_tile( point );
		while (row_size--)
		{
			if (!tile->read( stream, tile_version ))
				return false;
			tile++;
		}
	}

	t_tile_vertex* vertex;

	size = get_vertex_size();
	for (point.row = 0; point.row < size; point.row++)
	{
		point.column = get_tile_vertex_row_start( point.row );
		row_size = get_tile_vertex_row_end( point.row ) - point.column;
		vertex = &get_tile_vertex( point );
		while (row_size--)
		{
			vertex->set_height( get<t_uint8>( stream ));
			vertex++;
		}
	}
	recalculate_vertex_lighting();
	return true;
}

bool t_battlefield_terrain_map::write( std::streambuf& stream ) const
{
	t_map_point_2d	point;
	t_map_point_2d	offset;
	t_map_point_2d	new_point;
	int				size = get_size();
	int				row_size;
	t_tile const*	tile;

	put<t_int16>( stream, k_current_version );
	put<t_int16>( stream, t_abstract_tile::k_current_format_version );
	for (point.row = 0; point.row < size; point.row++)
	{
		point.column = get_tile_row_start( point.row );
		row_size = get_tile_row_end( point.row ) - point.column;
		tile = &get_tile( point );
		while (row_size--)
		{
			if (!tile->write( stream ))
				return false;
			tile++;
		}
	}

	t_tile_vertex const* vertex;

	size = get_vertex_size();
	for (point.row = 0; point.row < size; point.row++)
	{
		point.column = get_tile_vertex_row_start( point.row );
		row_size = get_tile_vertex_row_end( point.row ) - point.column;
		vertex = &get_tile_vertex( point );
		while (row_size--)
		{
			put<t_uint8>( stream, vertex->get_height() );
			vertex++;
		}
	}
	return true;
}
